package com.hqq.concurrency.read_write_lock;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;

/**
 * ReaderWriterLock
 * 读写锁
 * 控制读写权限
 * 允许多个读操作持有锁 但是如果加了写锁 读操作便会阻塞
 * 如果加了读锁 写操作就会阻塞
 * 是非公平锁
 * Created by heqianqian on 2017/7/30.
 */
public class ReaderWriterLock implements ReadWriteLock {


    private Object readerMutex = new Object();

    private int currentReaderCount;

    private ReadLock readLock = new ReadLock();

    private WriteLock writeLock = new WriteLock();

    /**
     * 全局互斥变量 用来表示写操作和读操作是否同时获得锁
     * <p>
     * 1.如果包含{@link readLock},表示当前线程持有读锁,其他线程仍然可以进行读操作
     * 2.如果包含{@link writeLock},表示当前线程持有写锁,其他线程既无法执行写操作也无法执行读操作
     */
    private Set<Object> globalMutex = new HashSet<>();

    @Override
    public Lock readLock() {
        return this.readLock;
    }

    @Override
    public Lock writeLock() {
        return this.writeLock;
    }

    private boolean doesWriterHoldLock() {
        return globalMutex.contains(writeLock);
    }

    private boolean doesReaderHoldLock() {
        return globalMutex.contains(readLock);
    }


    private boolean isLockFree() {
        return globalMutex.isEmpty();
    }

    private static void waitUnInterruptly(Object o) {
        try {
            o.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 读锁
     */
    private class ReadLock implements Lock {

        @Override
        public void lock() {
            synchronized (readerMutex) {
                currentReaderCount++;//当前读操作数加1
                if (currentReaderCount == 1) {
                    //尝试为第一个读线程获得互斥锁
                    synchronized (globalMutex) {
                        while (true) {
                            //如果没有线程持有锁或者当前持有的是读锁
                            if (isLockFree() || doesReaderHoldLock()) {
                                globalMutex.add(this);
                                break;
                            } else {//否则阻塞等待
                                waitUnInterruptly(globalMutex);
                            }
                        }
                    }
                }
            }

        }

        @Override
        public void lockInterruptibly() throws InterruptedException {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean tryLock() {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
            throw new UnsupportedOperationException();
        }

        @Override
        public void unlock() {
            synchronized (readerMutex) {
                //当前读线程数减1
                currentReaderCount--;
                //当读线程数为0[即完成所有读操作时 移除当前线程 唤醒其他等待线程]
                if (currentReaderCount == 0) {
                    synchronized (globalMutex) {
                        globalMutex.remove(this);
                        //唤醒等待者 一般是读锁
                        globalMutex.notifyAll();
                    }
                }
            }
        }

        @Override
        public Condition newCondition() {
            throw new UnsupportedOperationException();
        }
    }

    /**
     * 写锁
     */
    private class WriteLock implements Lock {

        @Override
        public void lock() {
            synchronized (globalMutex) {
                while (true) {
                    if (isLockFree()) {
                        globalMutex.add(this);
                        break;
                    } else if (doesWriterHoldLock()) {
                        waitUnInterruptly(globalMutex);
                    } else if (doesReaderHoldLock()) {
                        waitUnInterruptly(globalMutex);
                    } else {
                        throw new AssertionError("it should never reach here");
                    }
                }
            }
        }

        @Override
        public void lockInterruptibly() throws InterruptedException {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean tryLock() {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
            throw new UnsupportedOperationException();
        }

        @Override
        public void unlock() {
            synchronized (globalMutex) {
                globalMutex.remove(this);
                globalMutex.notifyAll();
            }
        }

        @Override
        public Condition newCondition() {
            throw new UnsupportedOperationException();
        }
    }
}
